package com.jctrip.hotel.source.service.elong.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.jctrip.hotel.common.constant.RedisKeyConstant;
import com.jctrip.hotel.common.enums.OrderStatusEnum;
import com.jctrip.hotel.common.enums.SourceEnum;
import com.jctrip.hotel.common.util.DateUtil;
import com.jctrip.hotel.common.util.JsonUtil;
import com.jctrip.hotel.common.util.RedisUtil;
import com.jctrip.hotel.domain.dao.HtHotelStdInfoDao;
import com.jctrip.hotel.domain.dao.HtPriceCalendarElongProductDao;
import com.jctrip.hotel.domain.entity.HtHotelStdInfo;
import com.jctrip.hotel.domain.entity.HtPriceCalendarElongProduct;
import com.jctrip.hotel.source.client.ElongClient;
import com.jctrip.hotel.source.model.OrderDataResult;
import com.jctrip.hotel.source.model.PriceCheckParam;
import com.jctrip.hotel.source.model.elong.ElongLocalEnum;
import com.jctrip.hotel.source.model.elong.req.*;
import com.jctrip.hotel.source.model.elong.req.offline.*;
import com.jctrip.hotel.source.model.elong.req.online.ElongHotelDetailReq;
import com.jctrip.hotel.source.model.elong.resp.*;
import com.jctrip.hotel.source.model.elong.resp.offline.*;
import com.jctrip.hotel.source.model.elong.resp.online.ElongHotelDetailResp;
import com.jctrip.hotel.source.model.wehotel.req.OrderData;
import com.jctrip.hotel.source.service.HotelCommonTaskService;
import com.jctrip.hotel.source.service.elong.ElongService;
import com.jctrip.hotel.source.util.ElongUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * @Author: zxy
 * @CreateTime: 2023-08-21 17:05
 * @Description:
 */
@Service
@Slf4j
public class ElongServiceImpl implements ElongService {
    private static Long LOST_TIME = 1000L;
    @Autowired
    private HotelCommonTaskService hotelCommonTaskService;
    @Autowired
    private HtPriceCalendarElongProductDao htPriceCalendarElongProductDao;
    @Autowired
    private HtHotelStdInfoDao htHotelStdInfoDao;
    @Autowired
    private ElongClient elongClient;
    @Autowired
    private RedisUtil redisUtil;


    @Override
    public String parseHotelId(String innId) {
        if (StringUtils.isBlank(innId)) {
            return null;
        }
        return innId.substring(SourceEnum.ELONGDOM.getCode().length());
    }

    @Override
    public String parseInnId(String hotelId) {
        if (null == hotelId) {
            return null;
        }
        return SourceEnum.ELONGDOM.getCode() + hotelId;
    }

    @Override
    public String[] getPostInfo(String hotelId, String roomTypeCode, String sourceId, String productCode) {
        Date date = new Date();
        HtPriceCalendarElongProduct htPriceCalendarElongProduct = htPriceCalendarElongProductDao.selectOne(
                new LambdaQueryWrapper<HtPriceCalendarElongProduct>()
                        .eq(HtPriceCalendarElongProduct::getHotelId, hotelId)
                        .eq(HtPriceCalendarElongProduct::getRoomTypeCode, roomTypeCode)
                        .eq(HtPriceCalendarElongProduct::getSourceId, sourceId)
                        .eq(HtPriceCalendarElongProduct::getProductCode, productCode)
                        .between(HtPriceCalendarElongProduct::getCalDate, DateUtil.beginOfDay(date), DateUtil.endOfDay(date)));
        if (Objects.isNull(htPriceCalendarElongProduct)) {
            throw new RuntimeException("今日该房型下该政策未选择价格推送推送");
        }
        String[] split = htPriceCalendarElongProduct.getProductId().split("-");
        return split;
    }

    @Override
    public ElongQueryCityResp queryElongCity(String user, String appKey, String secretKey, String local,
                                             ElongQueryCityReq req) {
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.STATIC_CITY.getCode(), req, local, ElongQueryCityResp.class);
    }

    @Override
    public ElongQueryHotelListResp queryElongHotelList(String user, String appKey, String secretKey, String local,
                                                       ElongQueryHotelListReq req) {
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.STATIC_LIST.getCode(), req, local, ElongQueryHotelListResp.class);
    }

    @Override
    public ElongQueryHotelResp queryElongHotel(String user, String appKey, String secretKey, String local,
                                               ElongQueryHotelReq req) {
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.STATIC_INFO.getCode(), req, local, ElongQueryHotelResp.class);
    }

    @Override
    public ElongQueryHotelRpResp queryElongHotelRp(String user, String appKey, String secretKey, String local,
                                                   ElongQueryHotelRpReq req) {
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.DATA_RP.getCode(), req, local, ElongQueryHotelRpResp.class);
    }

    @Override
    public ElongQueryInventoryResp queryElongInventory(String user, String appKey, String secretKey, String local,
                                                       ElongQueryInventoryReq req) {
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.DATA_INVENTORY.getCode(), req, local, ElongQueryInventoryResp.class);
    }

    @Override
    public ELongQueryRateResp queryElongRate(String user, String appKey, String secretKey, String local,
                                             ElongQueryRateReq req) {
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.DATA_RATE.getCode(), req, local, ELongQueryRateResp.class);
    }

    @Override
    public ElongValidateResp validateElongOrder(String user, String appKey, String secretKey, String local,
                                                ElongValidateReq req) {
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.DATA_VALIDATE.getCode(), req, local, ElongValidateResp.class);
    }

    @Override
    public ElongCreateOrderResp createElongOrder(String user, String appKey, String secretKey, String local,
                                                 ElongCreateOrderReq req) {
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.ORDER_CREATE.getCode(), req, local, ElongCreateOrderResp.class);
    }

    @Override
    public ElongPayOrderResp payElongOrder(String user, String appKey, String secretKey, String local,
                                           ElongPayOrderReq req) {
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.ORDER_PAY.getCode(), req, local, ElongPayOrderResp.class);
    }

    @Override
    public ElongQueryIncrIdResp queryElongIncrId(String user, String appKey, String secretKey, String local,
                                                 String incrType) {
        ElongQueryIncrIdReq elongQueryIncrIdReq = ElongQueryIncrIdReq.builder()
                .incrType(incrType)
                .lastTime(new Date())
                .build();
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.INCR_ID.getCode(), elongQueryIncrIdReq, local, ElongQueryIncrIdResp.class);
    }

    @Override
    public ElongQueryIncrIdResp queryELongShardId(String user, String appKey, String secretKey, String local, String incrType, Integer shardingKey) {
        ElongQueryShardIdReq elongQueryShardIdReq = ElongQueryShardIdReq.builder()
                .incrType(incrType)
                .lastTime(new Date())
                .shardingKey(shardingKey)
                .build();
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.INCR_SHARDING_ID.getCode(), elongQueryShardIdReq, local, ElongQueryIncrIdResp.class);
    }


    @Override
    public ElongQueryIncrOrderResp queryElongIncrOrder(String user, String appKey, String secretKey, String local,
                                                       ElongQueryIncrOrderReq req) {
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.INCR_ORDER.getCode(), req, local, ElongQueryIncrOrderResp.class);
    }

    @Override
    public ElongQueryShardStateResp queryElongShardState(String user, String appKey, String secretKey, String local, ElongQueryShardStateReq req) {
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.INCR_SHARDING_STATE.getCode(), req, local, ElongQueryShardStateResp.class);
    }

    @Override
    public ElongQueryOrderDetailResp queryElongOrderDetail(String user, String appKey, String secretKey, String local,
                                                           ElongQueryOrderDetailReq req) {
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.ORDER_DETAIL.getCode(), req, local, ElongQueryOrderDetailResp.class);
    }

    @Override
    public ElongCancelorderResp cancelElongOrder(String user, String appKey, String secretKey, String local, ElongCancelOrderReq req) {
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.ORDER_CANCEL.getCode(), req, local, ElongCancelorderResp.class);
    }


    @Override
    public OrderDataResult postOrder(String user, String appKey, String secretKey, String local,
                                     ElongCreateOrderReq req, PriceCheckParam priceCheck) {
        ElongQueryRateReq rateReq = ElongQueryRateReq.builder()
                .hotelIds(req.getHotelId())
                .hotelCodes(req.getHotelCode())
                .startDate(req.getArrivalDate())
                .endDate(DateUtils.addDays(req.getDepartureDate(), -1))
                .paymentType(ElongLocalEnum.PREPAY.getCode())
                .invoiceMode(ElongLocalEnum.ALL.getCode())
                .build();
        ELongQueryRateResp eLongQueryRateResp = queryElongRate(user, appKey, secretKey, local, rateReq);
        List<ELongQueryRateResp.Rate> rates = eLongQueryRateResp.getRates().stream()
                .filter(e -> e.getHotelId().equals(req.getHotelId())
                        && e.getHotelCode().equals(req.getHotelCode())
                        && e.getRoomTypeId().equals(req.getRoomTypeId())
                        && e.getRatePlanId().equals(req.getRatePlanId())).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(rates)) {
            throw new RuntimeException("房态信息不存在");
        }
        HtHotelStdInfo htHotelStdInfo = htHotelStdInfoDao.selectOne(new LambdaQueryWrapper<HtHotelStdInfo>()
                .eq(HtHotelStdInfo::getHotelId, parseInnId(req.getHotelId())));
        AtomicReference<BigDecimal> totalAdvanceRate = new AtomicReference<>(new BigDecimal(0));
        AtomicReference<BigDecimal> raisedTotalPrice = new AtomicReference<>(BigDecimal.valueOf(0));
        rates.forEach(rate -> {
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(rate.getStartDate());

            // 遍历日期
            while (!calendar.getTime().after(rate.getEndDate())) {
                // 对每一天进行操作
                Date currentDate = calendar.getTime();

                BigDecimal price = getPriceCalendarPrice(currentDate, rate, htHotelStdInfo);
                totalAdvanceRate.set(totalAdvanceRate.get().add(price));
                raisedTotalPrice.set(raisedTotalPrice.get().add(priceCheck.getRaiseFunc().apply(price)));

                // 日期加1天
                calendar.add(Calendar.DAY_OF_MONTH, 1);
            }
        });
        req.setTotalPrice(totalAdvanceRate.get().multiply(new BigDecimal(req.getNumberOfRooms())));
        if (priceCheck.getTotalRate()
                .compareTo(req.getTotalPrice()
                        .subtract(priceCheck.getTolerancePrice())) < 0) {
            String error = String.format("订单销售价不能低于采购总价，订单销售价=%s，采购总价=%s, 容差金额=%s, 目前差额=%s",
                    priceCheck.getTotalRate(),
                    totalAdvanceRate.get().multiply(new BigDecimal(req.getNumberOfRooms())),
                    priceCheck.getTolerancePrice(),
                    totalAdvanceRate.get().multiply(new BigDecimal(req.getNumberOfRooms())).subtract(priceCheck.getTotalRate()));
            hotelCommonTaskService.insertOrderErrorLog(req.getAffiliateConfirmationId(),
                    JsonUtil.toString(req), error);
            log.error("订单销售价不能低于采购总价，订单销售价={}，采购总价={}, 容差金额={}, 目前差额={}",
                    priceCheck.getTotalRate(),
                    raisedTotalPrice.get().multiply(new BigDecimal(req.getNumberOfRooms())),
                    priceCheck.getTolerancePrice(),
                    totalAdvanceRate.get().multiply(new BigDecimal(req.getNumberOfRooms())).subtract(priceCheck.getTotalRate()));
            throw new RuntimeException("订单销售价不能低于采购总价");
        }
        // 业务层面 不需要传发票信息
//        req.setInvoice(getInvoice(req.getTotalPrice()));
        // 进行下单前的试校验
        validateOrder(user, appKey, secretKey, local, req);
        ElongCreateOrderResp elongOrder = createElongOrder(user, appKey, secretKey, local, req);
        // 查询一下订单
        OrderData orderData = new OrderData();
        orderData.setOrderCode(String.valueOf(elongOrder.getOrderId()));
        ElongQueryOrderDetailReq orderDetailReq = ElongQueryOrderDetailReq.builder()
                .orderId(elongOrder.getOrderId())
                .affiliateConfirmationId(req.getAffiliateConfirmationId())
                .build();

        orderData.setOrderState(getOrderStatus(user, appKey, secretKey, local, orderDetailReq));

        return OrderDataResult.builder().orderState(orderData.getOrderState()).orderCode(orderData.getOrderCode())
                .externalId(req.getAffiliateConfirmationId()).totalRate(req.getTotalPrice())
                .requestJsonStr(JsonUtil.toString(req))
                .responseJsonStr(JsonUtil.toString(orderData)).build();
    }

    @Override
    public OrderData queryOrderStatus(String user, String appKey, String secretKey, String local, ElongQueryOrderDetailReq req) {
        ElongQueryOrderDetailResp orderDetailResp = queryElongOrderDetail(user, appKey, secretKey, local, req);
        if (Objects.isNull(orderDetailResp) || StringUtils.isBlank(orderDetailResp.getStatus())) {
            throw new RuntimeException("订单状态异常");
        }
        OrderData orderData = new OrderData();
        orderData.setExternalId(orderDetailResp.getAffiliateConfirmationId());
        orderData.setOrderCode(String.valueOf(orderDetailResp.getOrderId()));
        orderData.setOrderState(solveOrderStatus(orderDetailResp.getStatus()));
        return orderData;
    }

    @Override
    public ElongCancelorderResp cancelOrder(String user, String appKey, String secretKey, String local, ElongCancelOrderReq req) {
        ElongCancelorderResp elongCancelorderResp = cancelElongOrder(user, appKey, secretKey, local, req);
        if (!elongCancelorderResp.getSuccess()) {
            throw new RuntimeException("订单取消失败");
        }
        return elongCancelorderResp;
    }

    @Override
    public Long getLastId(String user, String appKey, String secretKey, String local, String incrType) {
        String key = String.format("%s-%s", RedisKeyConstant.SYNC_INCR_TYPE, incrType);
        if (redisUtil.get(key) == null) {
            ElongQueryIncrIdResp elongQueryIncrIdResp = queryElongIncrId(user, appKey, secretKey, local, incrType);
            redisUtil.set(key, elongQueryIncrIdResp.getLastId(), LOST_TIME);
        }
        return ((Number) redisUtil.get(key)).longValue();
    }

    @Override
    public Long getShardLastId(String user, String appKey, String secretKey, String local, String incrType, Integer shardingKey) {
        String key = String.format("%s-%s-%s", RedisKeyConstant.SYNC_INCR_TYPE, incrType, shardingKey);
        if (redisUtil.get(key) == null) {
            ElongQueryIncrIdResp elongQueryIncrIdResp = queryELongShardId(user, appKey, secretKey, local, incrType, shardingKey);
            redisUtil.set(key, elongQueryIncrIdResp.getLastId(), LOST_TIME);
        }
        return ((Number) redisUtil.get(key)).longValue();
    }

    @Override
    public ElongHotelDetailResp queryElongHotelDetail(String user, String appKey, String secretKey, String local, ElongHotelDetailReq req) {
        return getBaseRequest(user, appKey, secretKey, ElongLocalEnum.HOTEL_DETAIL.getCode(), req, local, ElongHotelDetailResp.class);
    }

    private Integer getOrderStatus(String user, String appKey, String secretKey, String local, ElongQueryOrderDetailReq orderDetailReq) {
        ElongQueryOrderDetailResp orderDetailResp = queryElongOrderDetail(user, appKey, secretKey, local, orderDetailReq);
        if (Objects.isNull(orderDetailResp) || StringUtils.isBlank(orderDetailResp.getStatus())) {
            throw new RuntimeException("订单状态异常");
        }
        return solveOrderStatus(orderDetailResp.getStatus());
    }

    private Integer solveOrderStatus(String orderStatus) {
        switch (orderStatus) {
            case "N":
            case "V":
                return 0;
            case "A":
                return 1;
            case "D":
            case "Z":
                return 2;
            case "B":
                return 3;
            case "F":
                return 4;
            case "C":
                return 5;
            case "S":
                return 9;
            case "E":
                return 21;
            case "B1":
            case "B2":
            case "B3":
                return 31;
            case "G":
                return 61;
            case "H":
            case "O":
            case "U":
                return 62;
            default:
                return 6;
        }
    }

    // 用于下单前的试校验
    private Boolean validateOrder(String user, String appKey, String secretKey, String local, ElongCreateOrderReq req) {
        ElongValidateReq validateReq = ElongValidateReq.builder()
                .arrivalDate(req.getArrivalDate())
                .departureDate(req.getDepartureDate())
                .earliestArrivalTime(req.getEarliestArrivalTime())
                .latestArrivalTime(req.getLatestArrivalTime())
                .hotelId(req.getHotelId())
                .roomTypeID(req.getRoomTypeId())
                .ratePlanId(req.getRatePlanId())
                .totalPrice(req.getTotalPrice())
                .numberOfRooms(req.getNumberOfRooms())
                .build();
        ElongValidateResp elongValidateResp = validateElongOrder(user, appKey, secretKey, local, validateReq);
        if (!"OK".equals(elongValidateResp.getResultCode())) {
            throw new RuntimeException(elongValidateResp.getErrorMessage());
        }
        return true;
    }

    private BigDecimal getPriceCalendarPrice(Date date, ELongQueryRateResp.Rate rate, HtHotelStdInfo htHotelStdInfo) {
        List<ElongQueryHotelResp.Supplier> suppliers = JsonUtil.toList(ElongQueryHotelResp.Supplier.class, htHotelStdInfo.getExtra());
        BigDecimal max = new BigDecimal("9999");
        if (org.springframework.util.CollectionUtils.isEmpty(suppliers)) {
            return max;
        }
        ElongQueryHotelResp.Supplier supplier = suppliers.stream()
                .filter(e -> rate.getHotelCode().equals(e.getHotelCode()))
                .findFirst().orElse(null);
        if (Objects.isNull(supplier)) {
            return max;
        }
        int day = DateUtil.dayForWeek(date);
        int start = supplier.getWeekendStart();
        int end = supplier.getWeekendEnd();
        BigDecimal none = new BigDecimal("-1");
        if (0 == start && 0 == end) {
            return rate.getMember().equals(none) ? max : rate.getMember();
        } else if (start < end) {
            return day > start && day <= end ?
                    rate.getWeekend().equals(none) ? max : rate.getWeekend()
                    : rate.getMember().equals(none) ? max : rate.getMember();
        } else {
            return day > end && day < start ?
                    rate.getMember().equals(none) ? max : rate.getMember()
                    : rate.getWeekend().equals(none) ? max : rate.getWeekend();
        }
    }

    /**
     * 将价格传入获取发票参数
     *
     * @param amount 下单时候的实时价格
     * @return
     */
    private ElongCreateOrderReq.Invoice getInvoice(BigDecimal amount) {
        ElongCreateOrderReq.Recipient recipient = ElongCreateOrderReq.Recipient.builder()
                .province("浙江省")
                .city("杭州市")
                .district("西湖区")
                .street("翠苑街道")
                .postalCode("310012")
                .phone("15605813961")
                .email("caiwu@jctech-inc.com")
                .build();
        ElongCreateOrderReq.Invoice invoice = ElongCreateOrderReq.Invoice.builder()
                .invoiceType("SpecialPaper")
                .titleType("Enterprise")
                .title("浙江嘉程商旅科技有限公司")
                .itin("91330110MA2J2HCE66")
                .itemName("代订房费")
                .amount(amount)
                .recipient(recipient)
                .taxPayerNum("91330110MA2J2HCE66")
                .taxRegisterBank("中国工商银行杭州众安支行")
                .registerBankNum("1202021709800254935")
                .registerAddress("浙江省杭州市余杭区仓前街道仓兴街1号4幢403-4室")
                .registerPhoneNum("15605813961")
                .build();
        return invoice;
    }

    private <T> T getBaseRequest(String user, String appKey, String secretKey,
                                 String method, Object reqParam, String local,
                                 Class<T> clazz) {
        Request req = new Request();
        req.setVersion("1.5");
        req.setLocal(StringUtils.isNotBlank(local) ? local : ElongLocalEnum.CN.getCode());
        req.setRequest(reqParam);
        String time = String.valueOf(System.currentTimeMillis() / 1000);
        String data = JsonUtil.toString(req);
        ElongResult elongResult = elongClient.callElong(user, method, time, ElongLocalEnum.JSON.getCode(), data,
                ElongUtil.getSign(appKey, secretKey, time, data));
        if (null == elongResult) {
            log.error("{} method error param{} result is null", method, data);
            throw new RuntimeException("result is null");
        }

        if (!"0".equals(elongResult.getCode())) {
            log.error("{} method error param{} result{}", method, data, JsonUtil.toString(elongResult));
            throw new RuntimeException(elongResult.getCode());
        }
        return JsonUtil.toObject(clazz, JsonUtil.toString(elongResult.getResult()));
    }
}
